﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MoroMoro.Samples.ToDoList.Base.Application;
using MoroMoro.Samples.ToDoList.Base.Persistence;

namespace MoroMoro.Samples.ToDoList.Base.Application
{
    /// <summary>
    /// 作業ファサードを表します。
    /// </summary>
    /// <remarks>
    /// <para>
    /// 作業ファサードは作業スペースのライフサイクルの管理を行います。
    /// </para>
    /// <para>
    /// 作業ファサードは各機能を実行するためのメソッド群をプレゼンテーションレイヤに提供します。
    /// 基本的にこのメソッド群は、作業スクリプトに処理を委譲しますが、
    /// 永続化エラーによって作業スペースが無効な状態になった場合には、
    /// 作業スペースのクリア及びエンティティの破棄を行う必要があります。
    /// これは、永続化を伴う作業スクリプトのエラーイベント発生時に、
    /// <see cref="CommitmentErrorsHandler"/> プロパティから取得したエラーハンドラにて永続化エラーを処理することで実装できます。
    /// <see cref="CommitmentErrorsHandler"/> プロパティから取得したエラーハンドラは、
    /// <see cref="Initialize"/> メソッドを呼び出して永続化エラーを対処します。
    /// <see cref="Initialize"/> メソッドは作業スペースのクリアを行った後に <see cref="OnInitialize"/> メソッドを呼び出します。
    /// 作業ファサードの派生クラスは <see cref="OnInitialize"/> メソッドをオーバーライドしてエンティティの破棄を行う必要があります。
    /// </para>
    /// <note type="tip">
    /// 永続化エラー発生時には、プレゼンテーションレイヤにも特殊な処理が要求されることがあります。
    /// 例えばエンティティの編集フォームで永続化エラーが発生した場合は OK ボタンを無効化するなどの処理がこれに該当します。
    /// これを行うには、プレゼンテーションレイヤでは永続化エラー専用のエラーハンドラを用意する必要がありますが、
    /// <see cref="ErrorsHandlerCreator&lt;THandler&gt;"/> クラスを利用することで、
    /// エラーハンドラの実装クラスの定義を省略し、動的にエラーハンドラを生成することができます。
    /// <code>
    /// protected void OnOkButtonClick(object sender, EventArgs e)
    /// {
    ///     // エラーハンドラを渡してエンティティの編集を実行.
    ///     _facade.ExecuteEdition(
    ///     (errors) =>
    ///     {
    ///         // エラー発生時に呼び出される処理.
    ///         
    ///         // エラーメッセージを構築して表示.
    ///         var errorMessage = new EditionErrorMessage();
    ///         error.HandledBy(errorMessage);
    ///         View.ShowErrorMessageBox(errorMessage.ToString());
    ///     
    ///         // エラーハンドラを動的に生成.
    ///         var commitmentErrorsHandler = ErrorsHandlerCreator&lt;ICommitmentErrorsHandler&gt;.Create(
    ///         () => 
    ///         {
    ///             // 永続化エラーが発生した場合に行う処理.
    ///             View.DisableOkButton();
    ///         });
    ///         errors.HandledBy(commitmentErrorsHandler);
    ///     });
    /// }
    /// </code>
    /// </note>
    /// </remarks>
    /// <example>
    /// 作業ファサードの実装方法を次のコード例に示します。
    /// <code>
    /// <![CDATA[
    /// using System;
    /// using System.Collections.Generic;
    /// using System.Collections.ObjectModel;
    /// using System.Linq;
    /// using System.Text;
    /// using MoroMoro.Samples.ToDoList.Base.Application;
    /// using MoroMoro.Samples.ToDoList.Application.ToDos;
    /// using MoroMoro.Samples.ToDoList.Application.Categories;
    /// using MoroMoro.Samples.ToDoList.Domain.ToDos;
    /// using MoroMoro.Samples.ToDoList.Domain.Categories;
    /// 
    /// namespace MoroMoro.Samples.ToDoList.Application
    /// {
    ///     [Serializable]
    ///     public sealed class ToDoListFacade : WorkFacade
    ///     {
    ///         #region Constructors
    /// 
    ///         public ToDoListFacade(WorkMode? mode = null)
    ///             : base(new WorkspaceSelector(mode).Create())
    ///         {
    ///         }
    /// 
    ///         #endregion
    /// 
    ///         #region Properties
    /// 
    ///         public ReadOnlyCollection<ToDo> ToDoList { get; private set; }
    /// 
    ///         public ReadOnlyCollection<Category> CategoryList { get; private set; }
    /// 
    ///         public ToDoState? StateFilter { get; private set; }
    /// 
    ///         public Category CategoryFilter { get; private set; }
    /// 
    ///         #endregion
    /// 
    ///         #region Events
    /// 
    ///         public event EventHandler Refreshed;
    /// 
    ///         public event ErrorEventHandler<ToDoListLoadErrors> ToDoListLoadError;
    /// 
    ///         public event ErrorEventHandler<CategoryListLoadErrors> CategoryListLoadError;
    /// 
    ///         #endregion
    /// 
    ///         #region Methods
    /// 
    ///         protected override void OnInitialize()
    ///         {
    ///             StateFilter = ToDoState.Touched;
    ///             CategoryFilter = null;
    /// 
    ///             LoadCategories(false);
    ///             LoadToDoList();
    ///         }
    /// 
    ///         private void LoadToDoList(bool raiseRefreshedEvent = true)
    ///         {
    ///             var script = new ToDoListLoadScript(Workspace);
    ///             script.StateFilter = StateFilter;
    ///             script.CategoryFilter = CategoryFilter;
    ///             script.Success += (sender, e) =>
    ///             {
    ///                 ToDoList = new ReadOnlyCollection<ToDo>(e.Outputs.List);
    ///                 if (raiseRefreshedEvent && (Refreshed != null))
    ///                 {
    ///                     Refreshed(this, EventArgs.Empty);
    ///                 }
    ///             };
    ///             script.Error += ToDoListLoadError;
    ///             script.Execute();
    ///         }
    /// 
    ///         private void LoadCategories(bool raiseRefreshedEvent = true)
    ///         {
    ///             var script = new CategoryListLoadScript(Workspace);
    ///             script.Success += (sender, e) =>
    ///             {
    ///                 CategoryList = new ReadOnlyCollection<Category>(e.Outputs.List);
    ///                 if (raiseRefreshedEvent && (Refreshed != null))
    ///                 {
    ///                     Refreshed(this, EventArgs.Empty);
    ///                 }
    ///             };
    ///             script.Error += CategoryListLoadError;
    ///             script.Execute();
    ///         }
    /// 
    /// 
    ///         public void ChangeToDoStateFilter(ToDoState? stateFilter)
    ///         {
    ///             if (StateFilter == stateFilter)
    ///             {
    ///                 return;
    ///             }
    /// 
    ///             StateFilter = stateFilter;
    ///             LoadToDoList();
    ///         }
    /// 
    ///         public void ChangeCategoryFilter(Category categoryFilter)
    ///         {
    ///             if (CategoryFilter == categoryFilter)
    ///             {
    ///                 return;
    ///             }
    /// 
    ///             CategoryFilter = categoryFilter;
    ///             LoadToDoList();
    ///         }
    /// 
    /// 
    ///         public ToDo.Builder CreateToDoBuilder()
    ///         {
    ///             var repository = Workspace.GetRepository<ToDo>();
    ///             var validator = new ToDoConstraintValidator(repository);
    ///             return new ToDo.Builder(validator);
    ///         }
    /// 
    ///         public ToDo.Builder CreateToDoBuilder(ToDo toDo)
    ///         {
    ///             var repository = Workspace.GetRepository<ToDo>();
    ///             var validator = new ToDoConstraintValidator(repository);
    ///             return new ToDo.Builder(validator, toDo);
    ///         }
    /// 
    /// 
    ///         public void ExecuteToDoCreation(ToDo.Builder builder, Action<ToDoCreationErrors> errorHandler)
    ///         {
    ///             var script = new ToDoCreationScript(Workspace);
    ///             script.Builder = builder;
    ///             script.Success += (sender, e) =>
    ///             {
    ///                 StateFilter = e.Outputs.Entity.State;
    ///                 CategoryFilter = e.Outputs.Entity.CategoryFilter;
    ///                 LoadToDoList();
    ///             };
    ///             script.Error += (sender, e) =>
    ///             {
    ///                 errorHandler(e.Errors);
    ///                 e.Errors.HandledBy(CommitmentErrorsHandler);
    ///             };
    ///             script.Execute();
    ///         }
    /// 
    ///         public void ExecuteToDoEdition(ToDo.Builder builder, Action<ToDoEditionErrors> errorHandler)
    ///         {
    ///             var script = new ToDoEditionScript(Workspace);
    ///             script.Builder = builder;
    ///             script.Success += (sender, e) =>
    ///             {
    ///                 LoadToDoList();
    ///             };
    ///             script.Error += (sender, e) =>
    ///             {
    ///                 errorHandler(e.Errors);
    ///                 e.Errors.HandledBy(CommitmentErrorsHandler);
    ///             };
    ///             script.Execute();
    ///         }
    /// 
    ///         public void ExecuteToDoDeletion(IEnumerable<ToDo> targets, Action<ToDoDeletionErrors> errorHandler)
    ///         {
    ///             var script = new ToDoDeletionScript(Workspace);
    ///             script.Targets = targets;
    ///             script.Success += (sender, e) =>
    ///             {
    ///                 LoadToDoList();
    ///             };
    ///             script.Error += (sender, e) =>
    ///             {
    ///                 errorHandler(e.Errors);
    ///                 e.Errors.HandledBy(CommitmentErrorsHandler);
    ///             };
    ///             script.Execute();
    ///         }
    /// 
    /// 
    ///         public Category.Builder CreateCategoryBuilder()
    ///         {
    ///             var repository = Workspace.GetRepository<Category>();
    ///             var validator = new CategoryConstraintValidator(repository);
    ///             return new Category.Builder(validator);
    ///         }
    /// 
    ///         public Category.Builder CreateCategoryBuilder(Category category)
    ///         {
    ///             var repository = Workspace.GetRepository<Category>();
    ///             var validator = new CategoryConstraintValidator(repository);
    ///             return new Category.Builder(validator, category);
    ///         }
    /// 
    /// 
    ///         public void ExecuteCategoryCreation(Category.Builder builder, Action<CategoryCreationErrors> errorHandler)
    ///         {
    ///             var script = new CategoryCreationScript(Workspace);
    ///             script.Builder = builder;
    ///             script.Success += (sender, e) =>
    ///             {
    ///                 LoadCategories();
    ///             };
    ///             script.Error += (sender, e) =>
    ///             {
    ///                 errorHandler(e.Errors);
    ///                 e.Errors.HandledBy(CommitmentErrorsHandler);
    ///             };
    ///             script.Execute();
    ///         }
    /// 
    ///         public void ExecuteCategoryEdition(Category.Builder builder, Action<CategoryEditionErrors> errorHandler)
    ///         {
    ///             var script = new CategoryEditionScript(Workspace);
    ///             script.Builder = builder;
    ///             script.Success += (sender, e) =>
    ///             {
    ///                 LoadCategories();
    ///             };
    ///             script.Error += (sender, e) =>
    ///             {
    ///                 errorHandler(e.Errors);
    ///                 e.Errors.HandledBy(CommitmentErrorsHandler);
    ///             };
    ///             script.Execute();
    ///         }
    /// 
    ///         public void ExecuteCategoryDeletion(IEnumerable<Category> targets, Action<CategoryDeletionErrors> errorHandler)
    ///         {
    ///             var script = new CategoryDeletionScript(Workspace);
    ///             script.Targets = targets;
    ///             script.Success += (sender, e) =>
    ///             {
    ///                 LoadCategories();
    ///             };
    ///             script.Error += (sender, e) =>
    ///             {
    ///                 errorHandler(e.Errors);
    ///                 e.Errors.HandledBy(CommitmentErrorsHandler);
    ///             };
    ///             script.Execute();
    ///         }
    /// 
    /// 
    ///         public void ExecuteToDoForward(IEnumerable<ToDo> targets, ToDoState state, Action<ToDoForwardErrors> errorHandler)
    ///         {
    ///             var script = new ToDoForwardScript(Workspace);
    ///             script.Targets = targets;
    ///             script.State = state;
    ///             script.Success += (sender, e) =>
    ///             {
    ///                 LoadToDoList();
    ///             };
    ///             script.Error += (sender, e) =>
    ///             {
    ///                 errorHandler(e.Errors);
    ///                 e.Errors.HandledBy(CommitmentErrorsHandler);
    ///             };
    ///             script.Execute();
    ///         }
    /// 
    ///         public void ExecuteToDoReversion(IEnumerable<ToDo> targets, ToDoState state, Action<ToDoReversionErrors> errorHandler)
    ///         {
    ///             var script = new ToDoReversionScript(Workspace);
    ///             script.Targets = targets;
    ///             script.State = state;
    ///             script.Success += (sender, e) =>
    ///             {
    ///                 LoadToDoList();
    ///             };
    ///             script.Error += (sender, e) =>
    ///             {
    ///                 errorHandler(e.Errors);
    ///                 e.Errors.HandledBy(CommitmentErrorsHandler);
    ///             };
    ///             script.Execute();
    ///         }
    /// 
    ///         #endregion
    ///     }
    /// }
    /// ]]>
    /// </code>
    /// </example>
    /// <seealso cref="CommitmentScript&lt;TOutputs, TErrors&gt;"/>
    /// <seealso cref="CommitmentErrors"/>
    /// <seealso cref="ErrorsHandlerCreator&lt;THandler&gt;"/>
    [Serializable]
    public abstract class WorkFacade : IDisposable
    {
        /// <summary>
        /// <see cref="WorkFacade"/> クラスの新しいインスタンスを初期化します。
        /// </summary>
        /// <param name="workspace">作業スペース。</param>
        /// <exception cref="System.ArgumentException">workspace が null です。</exception>
        protected WorkFacade(IWorkspace workspace)
        {
            if (workspace == null)
            {
                throw new ArgumentNullException("workspace");
            }
            _workspace = workspace;

            _commitmentErrorsHandler = ErrorsHandlerCreator<ICommitmentErrorsHandler>.Create(() =>
            {
                Initialize();
            });
        }


        /// <summary>
        /// 作業スペース。
        /// </summary>
        private readonly IWorkspace _workspace;

        /// <summary>
        /// 永続化エラーを <see cref="Initialize"/> メソッドを呼び出して対処するエラーハンドラ。
        /// </summary>
        private readonly ICommitmentErrorsHandler _commitmentErrorsHandler;


        /// <summary>
        /// 作業スペースを取得します。
        /// </summary>
        protected IWorkspace Workspace
        {
            get
            {
                return _workspace;
            }
        }

        /// <summary>
        /// 永続化エラーを <see cref="Initialize"/> メソッドを呼び出して対処するエラーハンドラを取得します。
        /// 永続化エラーが発生した場合、任意のエラー処理に加えて、必ずこのエラーハンドラでのエラー処理も行う必要があります。
        /// </summary>
        protected ICommitmentErrorsHandler CommitmentErrorsHandler
        {
            get
            {
                return _commitmentErrorsHandler;
            }
        }


        /// <summary>
        /// 作業ファサードを初期化します。
        /// このメソッドは作業スペースのクリアを行った後に <see cref="OnInitialize"/> メソッドを呼び出します。
        /// </summary>
        public void Initialize()
        {
            Workspace.Clear();
            OnInitialize();
        }

        /// <summary>
        /// 作業スペースのクリアを行った後に呼び出されます。
        /// 派生クラスでオーバーライドしてエンティティの破棄を行う必要があります。
        /// </summary>
        protected virtual void OnInitialize()
        {
        }


        /// <summary>
        /// リソースを解放します。
        /// </summary>
        public virtual void Dispose()
        {
            if (Workspace != null)
            {
                Workspace.Dispose();
            }
        }
    }
}
